meta:
  id: reftable
  title: git reftable file
  file-extension: ref
  ks-version: 0.9
  endian: be
  bit-endian: be
  ks-opaque-types: true
  imports:
    - varint
doc: |
  A reftable file is a portable binary file format customized for reference storage. References are sorted, enabling linear scans, binary search lookup, and range scans.
  Storage in the file is organized into variable sized blocks. Prefix compression is used within a single block to reduce disk space. Block size and alignment are tunable by the writer.
doc-ref:
  - https://git-scm.com/docs/reftable/2.45.2
  - https://about.gitlab.com/blog/2024/05/30/a-beginners-guide-to-the-git-reftable-format/
seq:
  - id: first_ref_block
    type: first_ref_block
  - id: other_ref_blocks
    type: ref_blocks
    size: footer.total_size_ref - footer.header.block_size
    if: footer.total_size_ref > footer.header.block_size and footer.ref_index_position != 0
  - id: ref_index_block
    type: index_block
    size: footer.total_size_ref_index
    if: footer.total_size_ref_index != 0
  - id: object_blocks
    type: object_blocks
    size: footer.total_size_object
    if: footer.total_size_object != 0
  - id: object_index_block
    type: index_block
    size: footer.total_size_object_index
    if: footer.total_size_object_index != 0
  - id: log_blocks
    type: log_blocks
    size: footer.total_size_log
    if: footer.total_size_log != 0
  - id: log_index_block
    type: index_block
    size: footer.total_size_log_index
    if: footer.total_size_log_index != 0
instances:
  footer:
    pos: _io.size - 68
    type: footer
types:
  header:
    seq:
      - id: magic
        contents: 'REFT'
      - id: version
        type: u1
      - id: block_size
        type: b24
      - id: min_update_index
        type: u8
      - id: max_update_index
        type: u8
  footer:
    seq:
      - id: header
        type: header
      - id: ref_index_position
        type: u8
      - id: object_info
        type: u8
      - id: object_index_position
        type: u8
      - id: log_position
        type: u8
      - id: log_index_position
        type: u8
      - id: crc32
        type: u4
    instances:
      object_position:
        value: object_info >> 5
      object_id_len:
        value: object_info & 0b00011111
      total_size_ref:
        value: "ref_index_position > 0 ? ref_index_position : (object_position > 0 ? object_position : (log_position > 0 ? log_position : (_io.size - 68)))"
      total_size_ref_index:
        value: "ref_index_position == 0 ? 0 : ((object_position > 0 ? object_position : (log_position > 0 ? log_position : (_io.size - 68))) - ref_index_position)"
      total_size_object:
        value: "object_position == 0 ? 0 : ((object_index_position > 0 ? object_index_position : (log_position > 0 ? log_position : (_io.size - 68))) - object_position)"
      total_size_object_index:
        value: "object_index_position == 0 ? 0 : ((log_position > 0 ? log_position : (_io.size - 68)) - object_index_position)"
      total_size_log:
        value: "log_position == 0 ? 0 : ((log_index_position > 0 ? log_index_position : (_io.size - 68)) - log_position)"
      total_size_log_index:
        value: "log_index_position == 0 ? 0 : ((_io.size - 68) - log_index_position)"

  ######################################### ref_block
  ref_blocks:
    seq:
      - id: records
        type: ref_block
        size:  _root.footer.header.block_size
        repeat: eos
    instances:
      total_count:
        value: records.size
  first_ref_block:
    seq:
      - id: header
        type: header
      - id: ref_block_flag
        type: u1
        valid: 0x72
      - id: ref_block_len
        type: b24
      - id: ref_records
        type: ref_records
        size: ref_block_len - _io.pos - restart_count * 3 - 2
      - id: restart_offset
        type: b24
        repeat: expr
        repeat-expr: restart_count
      - id: i_restart_count
        type: u2
      - id: block_padding
        type: u1
        repeat: expr
        repeat-expr: _root.footer.header.block_size - ref_block_len
        if: _root.footer.ref_index_position != 0 and _root.footer.header.block_size != 0
    instances:
      restart_count:
        pos: ref_block_len - 2
        type: u2
  ref_block:
    seq:
      - id: ref_block_flag
        type: u1
        valid: 0x72
      - id: ref_block_len
        type: b24
      - id: ref_records
        type: ref_records
        size: ref_block_len - _io.pos - restart_count * 3 - 2
      - id: restart_offset
        type: b24
        repeat: expr
        repeat-expr: restart_count
      - id: i_restart_count
        type: u2
      - id: block_padding
        type: u1
        repeat: expr
        repeat-expr: _root.footer.header.block_size - ref_block_len
        if: _root.footer.ref_index_position != 0 and _root.footer.header.block_size != 0
    instances:
      restart_count:
        io: _io
        pos: ref_block_len - 2
        type: u2

  ref_records:
    seq:
      - id: records
        type: ref_record
        repeat: eos
    instances:
      total_count:
        value: records.size
        
  ref_record:
    seq:
      - id: ref_prefix_length
        type: varint
      - id: ref_suffix_info
        type: varint
      - id: ref_suffix
        type: str
        encoding: ASCII
        size: ref_suffix_length
      - id: ref_update_index_delta
        type: varint
      - id: value
        type: 
          switch-on: ref_value_type
          cases:
            0: ref_record_value_deletion
            1: ref_record_value_one_object_name
            2: ref_record_value_two_object_names
            3: ref_record_value_symbolic_reference
    instances:
      ref_suffix_length:
        value: ref_suffix_info.value >> 3
      ref_value_type:
        value: ref_suffix_info.value & 0b00000111
  ref_record_value_deletion:
    seq:
      - id: no_value
        size: 0
  ref_record_value_one_object_name:
    seq:
      - id: oid
        size: 20
  ref_record_value_two_object_names:
    seq:
      - id: oid
        size: 20
      - id: peeled_oid
        size: 20
  ref_record_value_symbolic_reference:
    seq:
      - id: target_ref_len
        type: varint
      - id: target_ref
        type: str
        size: target_ref_len.value
        encoding: ASCII

  ######################################### index_block
  index_block:
    seq:
      - id: index_block_flag
        type: u1
        valid: 0x69
      - id: index_block_len
        type: b24
      - id: index_records
        type: index_records
        size: index_block_len - _io.pos - restart_count * 3 - 2
      - id: restart_offset
        type: b24
        repeat: expr
        repeat-expr: restart_count
      - id: i_restart_count
        type: u2
      - id: block_padding
        type: u1
        repeat: eos
    instances:
      restart_count:
        io: _io
        pos: index_block_len - 2
        type: u2

  index_records:
    seq:
      - id: records
        type: index_record
        repeat: eos
    instances:
      total_count:
        value: records.size
        
  index_record:
    seq:
      - id: ref_prefix_length
        type: varint
      - id: ref_suffix_info
        type: varint
      - id: ref_suffix
        type: str
        encoding: ASCII
        size: ref_suffix_length
      - id: block_position
        type: varint
    instances:
      ref_suffix_length:
        value: ref_suffix_info.value >> 3
        
  ######################################### object_block
  object_blocks:
    seq:
      - id: records
        type: object_block
        size:  _root.footer.header.block_size
        repeat: eos
    instances:
      total_count:
        value: records.size
  object_block:
    seq:
      - id: object_block_flag
        type: u1
        valid: 0x6f
      - id: object_block_len
        type: b24
      - id: object_records
        type: object_records
        size: object_block_len - _io.pos - restart_count * 3 - 2
      - id: restart_offset
        type: b24
        repeat: expr
        repeat-expr: restart_count
      - id: i_restart_count
        type: u2
      - id: block_padding
        type: u1
        repeat: expr
        repeat-expr: _root.footer.header.block_size - object_block_len
        if: _root.footer.object_index_position != 0 and _root.footer.header.block_size != 0
    instances:
      restart_count:
        io: _io
        pos: object_block_len - 2
        type: u2

  object_records:
    seq:
      - id: records
        type: object_record
        repeat: eos
    instances:
      total_count:
        value: records.size
        
  object_record:
    seq:
      - id: object_prefix_length
        type: varint
      - id: object_suffix_info
        type: varint
      - id: object_suffix
        type: str
        encoding: ASCII
        size: object_suffix_length
      - id: object_cnt_large
        type: varint
        if: object_cnt_3 == 0
      - id: position_delta
        type: varint
        repeat: expr
        repeat-expr: object_position_count
        if: object_position_count > 0
    instances:
      object_suffix_length:
        value: object_suffix_info.value >> 3
      object_cnt_3:
        value: object_suffix_info.value & 0b00000111
      object_position_count:
        value: "object_cnt_3 == 0 ? object_cnt_large.value : object_cnt_3"
        
  ######################################### log_block
  log_blocks:
    seq:
      - id: records
        type: log_block
        repeat: eos
    instances:
      total_count:
        value: records.size
  log_block:
    seq:
      - id: log_block_flag
        type: u1
        valid: 0x67
      - id: log_block_len
        type: b24
      - id: zlib_deflate
        type: zlib_inflate_log_block
        size: 0
        process: cp3.kaitai.zlib.inflate_zlib(_io)
    instances:
      data:
        value: log_block_len
  zlib_inflate_log_block:
    seq:
      - id: log_records
        type: log_records
        size: _io.size - restart_count * 3 - 2
      - id: restart_offset
        type: b24
        repeat: expr
        repeat-expr: restart_count
      - id: i_restart_count
        type: u2
    instances:
      restart_count:
        pos: _io.size - 2
        type: u2
    
  log_records:
    seq:
      - id: records
        type: log_record
        repeat: eos
    instances:
      total_count:
        value: records.size
        
  log_record:
    # log record ref key format: ref_name '\0' reverse_int64( update_index )
    seq:
      - id: log_key_prefix_length
        type: varint
      - id: log_key_suffix_info
        type: varint
      - id: log_key_suffix
        size: log_key_suffix_length
      - id: log_data
        type: log_data
        if: log_value_type == 1
    instances:
      log_key_suffix_length:
        value: log_key_suffix_info.value >> 3
      log_value_type:
        value: log_key_suffix_info.value & 0b00000111
  log_data:
    seq:
      - id: old_id
        size: 20
      - id: new_id
        size: 20
      - id: name_length
        type: varint
      - id: name
        type: str
        encoding: ASCII
        size: name_length.value
      - id: email_length
        type: varint
      - id: email
        type: str
        encoding: ASCII
        size: email_length.value
      - id: time_seconds
        type: varint
      - id: tz_offset
        type: s2
      - id: message_length
        type: varint
      - id: message
        type: str
        encoding: ASCII
        size: message_length.value
